﻿Imports System.ComponentModel
Imports System.IO
Imports System.IO.File
Imports System.Reflection
Imports System.Security
Imports System.Security.Cryptography
Imports System.Xml
Imports System.Xml.XPath
Imports System.Xml.Xsl
Imports Microsoft.Win32

Module Module1

    Private xmlFilePath As String
    Public Property DataXmlFilePath() As String
        Get
            Return XmlFilePath
        End Get
        Set(ByVal value As String)
            xmlFilePath = value
        End Set
    End Property

    Private username As String
    Public Property AlternativeUsername() As String
        Get
            Return username
        End Get
        Set(ByVal value As String)
            username = value
        End Set
    End Property

    Private securePassword As Security.SecureString
    Public Property AlternativePassword() As Security.SecureString
        Get
            Return securePassword
        End Get
        Set(ByVal value As Security.SecureString)
            securePassword = value
        End Set
    End Property

    Private FIMServerAddress As String
    Public Property FIMServer As String
        Get
            Return FIMServerAddress
        End Get
        Set(ByVal value As String)
            FIMServerAddress = value
        End Set
    End Property

    Private backgroundWorker As BackgroundWorker = New BackgroundWorker
    Private oProcess As Process = New System.Diagnostics.Process
    Private entropyBytes As Byte() = {4, 5, 7, 3, 4, 5, 7, 9}

    Sub OpenDataFolder()
        Dim path As String = My.Application.Info.DirectoryPath & "\Res\Data"

        Dim oProcess As New System.Diagnostics.Process
        oProcess.StartInfo.FileName = "Explorer"
        Dim startArguments As String = "/r, """ & path & """"

        oProcess.StartInfo.Arguments = startArguments
        oProcess.Start()
        oProcess.WaitForExit()
    End Sub

    Function GetXmlFilePath() As String
        Try
            If String.IsNullOrEmpty(DataXmlFilePath) Then
                DataXmlFilePath = My.Application.Info.DirectoryPath & "\Res\Data\Data.xml"
            End If

            GetXmlFilePath = DataXmlFilePath
            If Not File.Exists(GetXmlFilePath) Then
                Throw New Exception("Data file not found!" & vbCrLf & "Please retrieve data from FIM.")
            End If

        Catch ex As Exception
            Throw ex
        End Try
    End Function

    Function Transform(ByVal xslFile As String, ByVal xmlDoc As XmlDocument, ByVal xslArg As String) As String
        Try
            Dim XslFilePath As String = My.Application.Info.DirectoryPath & "\Res\Xslt\" & xslFile
            If Not File.Exists(XslFilePath) Then
                Throw New Exception("XSLT file not found: " & xslFile)
            End If

            Dim xmlTextReader As New XmlTextReader(XslFilePath)
            Dim memStream As MemoryStream = New MemoryStream
            Dim xsltArgs As XsltArgumentList = New XsltArgumentList

            If (xslArg.Length > 0) Then
                xsltArgs.AddParam("filter", "", xslArg)
            End If

            Dim xslSettings As New XsltSettings
            xslSettings.EnableScript = True

            Dim xslTrans As New XslCompiledTransform
            xslTrans.Load(XmlTextReader, xslSettings, New XmlUrlResolver)
            xslTrans.Transform(xmlDoc.CreateNavigator, xsltArgs, memStream)

            Dim stmReader As StreamReader = New StreamReader(memStream)
            memStream.Position = 0

            Dim sRetVal As String = stmReader.ReadToEnd
            stmReader.Close()
            Return sRetVal
        Catch ex As Exception
            Throw ex
        End Try
    End Function

    Sub RunPSScript(ByVal scriptName As String, ByVal scriptParam As String)
        backgroundWorker.WorkerReportsProgress = True
        backgroundWorker.WorkerSupportsCancellation = True
        AddHandler backgroundWorker.DoWork, AddressOf backgroundWorker_DoWork
        AddHandler backgroundWorker.ProgressChanged, AddressOf backgroundWorker_ProgressChanged
        AddHandler backgroundWorker.RunWorkerCompleted, AddressOf backgroundWorker_RunWorkerCompleted

        '----------------------------------------------------------------------------------------------
        If Not CheckRegistry() Then
            Throw New Exception("PowerShell is not configured to run scripts." & vbCrLf & _
                                "Please check your ExecutionPolicy configuration.")
        End If
        '----------------------------------------------------------------------------------------------
        Dim scriptPath As String = My.Application.Info.DirectoryPath & "\Res\PowerShell\" & scriptName
        If Not File.Exists(scriptPath) Then
            Throw New Exception("PowerShell script not found: " + vbCrLf + scriptPath)
        End If
        '----------------------------------------------------------------------------------------------

        ' Initiate a new instance of the custom EventArgs class for the process.
        Dim eventArgs As ProcessEventArgs = New ProcessEventArgs()
        eventArgs.ScriptPath = scriptPath

        ' Run the PowerShell as BackgroundWorker thread
        If Not backgroundWorker.IsBusy = True Then
            backgroundWorker.RunWorkerAsync(eventArgs)
        End If
    End Sub

    Private Sub backgroundWorker_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs)
        Dim worker As BackgroundWorker = CType(sender, BackgroundWorker)
        Dim eventArgs As ProcessEventArgs = TryCast(e.Argument, ProcessEventArgs)

        'Dim scriptPath As String = DirectCast(e.Argument, String)
        Dim scriptPath As String = eventArgs.ScriptPath

        Try
            worker.ReportProgress(0)

            oProcess.StartInfo.FileName = "PowerShell"
            Dim startArguments As String = " & '" & scriptPath & "'" & " '" & FIMServer & "'"

            oProcess.StartInfo.Arguments = startArguments
            oProcess.StartInfo.RedirectStandardOutput = True
            oProcess.StartInfo.RedirectStandardError = True
            oProcess.StartInfo.UseShellExecute = False

            ' Verify if alternative credentials have been configured
            If (Not String.IsNullOrEmpty(AlternativeUsername)) And (AlternativePassword IsNot Nothing) Then
                oProcess.StartInfo.UserName = AlternativeUsername
                oProcess.StartInfo.Password = AlternativePassword
            End If

            oProcess.Start()
            'oProcess.WaitForExit()

            Dim output As String = String.Empty
            Do
                output += oProcess.StandardOutput.ReadToEnd()
            Loop Until oProcess.HasExited

            eventArgs.ExitCode = oProcess.ExitCode
            eventArgs.Output = output

            If (oProcess.ExitCode <> 0) AndAlso (oProcess.ExitCode <> 1) Then
                ' Indicate that the process has been canceled by the user
                e.Cancel = True
            End If

        Catch ex As Exception
            If String.Equals(ex.Message, "The stub received bad data") Then
                MsgBox("Bad username or password has been specified.", MsgBoxStyle.Exclamation, My.Application.Info.Title)
            Else
                MsgBox(ex.Message, MsgBoxStyle.Exclamation, My.Application.Info.Title)
            End If

        Finally
            If Not oProcess Is Nothing Then
                oProcess.Close()
            End If

            e.Result = eventArgs
        End Try
    End Sub

    Private Sub backgroundWorker_RunWorkerCompleted(ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs)
        If (e.Error IsNot Nothing) Then
            MsgBox(e.Error.Message, MsgBoxStyle.Critical, My.Application.Info.Title)
        ElseIf e.Cancelled Then
            MsgBox("The operation has been canceled manually, no data have been loaded.", MsgBoxStyle.Exclamation, My.Application.Info.Title)
        Else
            Dim eventArgs As ProcessEventArgs = TryCast(e.Result, ProcessEventArgs)

            If EventArgs.ExitCode = 0 Then
                MsgBox("The operation has been completed sucessfully.", MsgBoxStyle.Information, My.Application.Info.Title)
            Else
                Dim output As String = "An exception has been thrown during the operation: " & EventArgs.Output
                MsgBox(output, MsgBoxStyle.Exclamation, My.Application.Info.Title)
            End If
        End If

        Form1.ToolStripStatusLabel1.Text = "Ready"

        RemoveHandler backgroundWorker.DoWork, AddressOf backgroundWorker_DoWork
        RemoveHandler backgroundWorker.ProgressChanged, AddressOf backgroundWorker_ProgressChanged
        RemoveHandler backgroundWorker.RunWorkerCompleted, AddressOf backgroundWorker_RunWorkerCompleted
    End Sub

    Private Sub backgroundWorker_ProgressChanged(ByVal sender As Object, ByVal e As ProgressChangedEventArgs)
        Form1.ToolStripStatusLabel1.Text = "Retrieving data..."
    End Sub

    Private Function CheckRegistry() As Boolean
        Try
            Dim registryKey As RegistryKey = Registry.LocalMachine.OpenSubKey("SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell")

            If IsNothing(registryKey.GetValue("ExecutionPolicy")) Then
                Return False
            End If
            Return registryKey.GetValue("ExecutionPolicy").Equals("Unrestricted")

        Catch ex As Exception
            Throw ex
        End Try
    End Function

    ''' <summary>
    ''' Encrypts a clear text.
    ''' </summary>
    ''' <param name="plainText">The plain text.</param>
    ''' <returns>The cipher text.</returns>
    Public Function EncryptString(ByVal plainText As String) As String
        Dim plainBytes As Byte()
        Dim encryptedBytes As Byte()
        Dim cipherText As String

        ' Convert the string value
        plainBytes = System.Text.Encoding.Unicode.GetBytes(plainText)

        ' Encrypt the string value (plain text) with the DPAPI
        encryptedBytes = ProtectedData.Protect(plainBytes, entropyBytes, DataProtectionScope.CurrentUser)

        ' Convert the byte array and return it
        cipherText = Convert.ToBase64String(encryptedBytes)
        Return cipherText
    End Function

    ''' <summary>
    ''' Decrypts a cipher text.
    ''' </summary>
    ''' <param name="cipherText">The cipher text.</param>
    ''' <returns>The clear text.</returns>
    Public Function DecryptString(ByVal cipherText As String) As String
        Dim cipherBytes As Byte()
        Dim decryptedBytes As Byte()
        Dim plainText As String

        ' Convert the string value
        cipherBytes = Convert.FromBase64String(cipherText)

        ' Decrypt the string value (cipher text) with the DPAPI
        decryptedBytes = ProtectedData.Unprotect(cipherBytes, entropyBytes, DataProtectionScope.CurrentUser)

        ' Convert the byte array and return it
        plainText = System.Text.Encoding.Unicode.GetString(decryptedBytes)
        Return plainText
    End Function
End Module

''' <summary>
''' Custom EventArgs class used for the process executed in a BackgroundWorker thread.
''' </summary>
''' <remarks></remarks>
Public Class ProcessEventArgs

    Public Property ExitCode() As Integer
        Get
            Return processExitCode
        End Get
        Set(ByVal value As Integer)
            processExitCode = value
        End Set
    End Property
    Private processExitCode As Integer

    Public Property ScriptPath() As String
        Get
            Return processScriptPath
        End Get
        Set(ByVal value As String)
            processScriptPath = value
        End Set
    End Property
    Private processScriptPath As String

    Public Property Output() As String
        Get
            Return processOutput
        End Get
        Set(ByVal value As String)
            processOutput = value
        End Set
    End Property
    Private processOutput As String

End Class